﻿#-*- coding: utf-8 -*-
# author: vx(wanjochan)
# date: 2022-04-30

"""
# 无依赖暴露QMT接口(ipc模式)
https://gitee.com/miguoliang/shared-data/blob/master/tools/svr_qmtipc.py

# 演示客户端
https://gitee.com/miguoliang/shared-data/blob/master/tools/clt_ipc.py

注意，

由于QMT是c++管理线程会有阻塞，所以如果选择暴露接口，那么这个QMT实例就专注暴露，不要跑策略

另外分享每天重启的bat脚本(路径自己改一下)，也主要qmt要设置自动登陆和启动策略

taskkill /F /IM:XtItClient.exe 
move /y d:/qmt20220113/userdata/log d:/qmt_userdata_log
D:/qmt20220113/bin.x64/XtItClient.exe 

"""


g_acct = '66301009048' # replace to your acct of qmt
#address = r'\\.\Pipe\qmtipc007' # win-named-pipe mode
#address = ('127.0.0.1',16688)  # host:port mode
address = ('0.0.0.0',16667)  # not safe ;)





def tryx(l,e=print):
    try: return l()
    except Exception as ex: return ex if True==e else e(ex) if e else None

import json
class MyJsonEncoder(json.JSONEncoder):
    def default(self, obj): return tryx(lambda:json.JSONEncoder.default(self,obj),str)
s2o = lambda s:tryx(lambda:json.loads(s))
o2s = lambda o,indent=None:tryx(lambda:json.dumps(o, indent=indent, ensure_ascii=False, cls=MyJsonEncoder))

def time_maker(days=0,date=None,outfmt=None,infmt='%Y-%m-%d',
        months=0):
    from datetime import datetime,timedelta
    from time import mktime
    if date is None: _dt = datetime.now()
    else: _dt = datetime.fromtimestamp(int(date))\
        if infmt=='0' or not infmt\
        else datetime.strptime(str(date),infmt)
    if months>0 or months<0:
        from dateutil.relativedelta import relativedelta
        _dt += relativedelta(months=months)
    _dt += timedelta(days=days)
    if outfmt is None: outfmt = infmt
    if outfmt=='0' or not outfmt:
        return int(mktime(_dt.timetuple()))
    return _dt.strftime(outfmt)

#(lambda o:[(k,getattr(o,k)) for k in dir(o) if 'Order' in k])(get_trade_detail_data(g_acct,'stock','order')[0])
#[["m_dOrderPriceRMB", 1.7976931348623157e+308], ["m_nOrderPriceType", 50], ["m_nOrderStatus", 56], ["m_nOrderStrategyTyp
#e", 3145786], ["m_nOrderSubmitStatus", 51], ["m_strOrderParam", ""], ["m_strOrderRef", "1697620407772774430"], ["m_strOr
#derStrategyType", "函数下单"], ["m_strOrderSysID", "2971"]]
def getattr(o,k):
  gg = globals()
  ll = locals()
  return tryx(lambda:eval(f'o.{k}',gg,ll))

import threading
def yielder(func,wrap=tryx,do_yield=True):
  rt = wrap(func) if wrap else func()
  yield rt

def yielder_loop(func,wrap=tryx,do_yield=True):
  while True:
    rt = yield from yielder(func,wrap,do_yield)
    #rt = wrap(func) if wrap else func()
    if do_yield and rt is not None: yield rt

try_async=lambda func:threading.Thread(target=func).start()

tryx_async = lambda func:tryx(try_async)

my_eval = eval
my_encode = str

g_ctx = None
server = None
server_i = 0

g_ctx = None

def my_acct(acct=None):
    global g_acct
    if acct: g_acct = acct
    if not g_acct: raise Exception('empty g_acct') 
    return g_acct

def my_pos(accttype='stock',datatype='position',acct=None):
    rt = []
    debug = 0
    for obj in get_trade_detail_data(acct or my_acct(),accttype,datatype):
        code6 = str(obj.m_strInstrumentID)
        mk2 = str(obj.m_strExchangeID)
        vol = float(obj.m_nVolume)
        mkp = float(obj.m_dMarketValue)
        prz = float(obj.m_dSettlementPrice)
        #last = float(obj.m_nYesterdayVolume) # TODO
        last = float(obj.m_dLastPrice)
        rt.append([code6,mk2,vol,mkp,prz,last])
        if debug<1:
            #for k in obj: print(k)
            print(obj)
        debug+=1
    return rt

def my_last_order_id(accttype='stock',datatype='order',acct=None):
    return get_last_order_id(acct or my_acct(),accttype,datatype)

def my_order(code,amt,prz=0,sleep_order_id=0,acct=None):
    global g_ctx
    if not acct: acct = my_acct()
    rst = passorder(23 if amt>0 else 24,1101,acct,code,11 if prz>0 else 14,prz,abs(amt),2,g_ctx)
    print('my_order:',code,amt,prz)
    if sleep_order_id>0:
        sleep(sleep_order_id)
        order_id = my_last_order_id(acct=acct)
        print('.order_id=',order_id)
        return order_id
    print('=>',rst)
    return rst

#very dangerous...
#my_pos_clear=lambda:[my_order('{}.{}'.format(v[0],v[1]),-round(v[2])) for v in my_pos() if v[2]>0]

# price only, deprecated!
my_price=lambda sector='沪深A股':dict([(v[0],round(v[1]['lastPrice'],3)) for v in g_ctx.get_full_tick(get_stock_list_in_sector(sector)).items()])

#my_price2=lambda sector='沪深A股':dict([(v[0],[round(v[1]['lastPrice'],3),round(v[1]['lastClose'] or 0,3),v[1]['timetag']]) for v in g_ctx.get_full_tick(get_stock_list_in_sector(sector)).items()])

# which having lastClose
my_price2=lambda sector='沪深A股':dict([(v[0],[round(v[1]['lastPrice'],3),round(v[1]['lastClose'] or 0,3)]) for v in g_ctx.get_full_tick(get_stock_list_in_sector(sector)).items()])

# for old v1...(lastClose
my_price_v1=lambda sector='沪深A股':dict([(v[0],[round(v[1]['lastPrice'],3),round(v[1]['lastClose'] or 0,3)]) for v in g_ctx.get_full_tick(['000001.SH','399001.SZ','399006.SZ','399300.SZ','399905.SZ']+get_stock_list_in_sector(sector)).items()])

my_prod_a = lambda sector:get_stock_list_in_sector(sector)

l_hs_300 = my_prod_a('沪深300')
l_hs_idx = my_prod_a('沪深指数')
l_hs_agu = my_prod_a('沪深A股')
l_hs_kzz = my_prod_a('沪深转债')

l_hs_etf = my_prod_a('沪深ETF')
l_hs_zq = my_prod_a('沪深债券')
l_hs_jj = my_prod_a('沪深基金')

list_a_default = l_hs_agu+l_hs_kzz+l_hs_etf+l_hs_idx
#field_a_default = ['lastPrice','lastClose','timetag']
field_a_default = ['lastPrice','lastClose']
#field_a_ext_default = ['lmt','dlt']
field_a_ext_default = ['lmt']
def pull_price(lmt=None,list_a=list_a_default,field_a=field_a_default,field_a_ext=field_a_ext_default):
  rt = {}
  if lmt is None: lmt = time_maker(-7,outfmt='')
  for k,v in g_ctx.get_full_tick(list_a).items():
    o = {}
    v_lmt = time_maker(date=v['timetag'][:17],infmt="%Y%m%d %H:%M:%S",outfmt='')
    if v_lmt>lmt:
      for field in field_a:
        o[field] = v[field]
        if 'lmt' in field_a_ext: o['lmt'] = v_lmt
        if 'dlt' in field_a_ext: o['dlt'] = v_lmt - lmt
      rt[k] = o
  return rt

def pull_price_a(lmt=None,list_a=list_a_default,field_a=field_a_default,field_a_ext=field_a_ext_default):
  o = pull_price(lmt,list_a,field_a)
  rt_head = ['code','lastPrice','lastClose']
  if 'lmt' in field_a_ext: rt_head.append('lmt')
  if 'dlt' in field_a_ext: rt_head.append('dlt')
  rt = [rt_head]
  for k,v in o.items():
    lastPrice = v['lastPrice']
    lastClose = v['lastClose']
    if lastPrice>0:
      rt_row = [k,lastPrice, lastClose]
      if 'lmt' in field_a_ext: rt_row.append(v['lmt'])
      if 'dlt' in field_a_ext: rt_row.append(v['dlt'])
      rt.append(rt_row)
  return rt

def handle_bin(conn):
	s = tryx(lambda:conn.recv(),False)
	if s is None:
		print('handle_bin nothing recv',type(conn))
		return
	rt = tryx(lambda:str(my_eval(s)),str)
	print('handle_bin',s,'=>',rt)
	conn.send(my_encode(rt))

def start_server():
	global server,server_i
	if server_i>0:
		print('start_server() quick for server is not None')
		return

	from multiprocessing.connection import Listener
	server = Listener(address=address)

	server_i+=1
	print(f'try start_server at {address}',type(server),server_i)

	while True:
		print('round')
		for conn in yielder(server.accept,wrap=lambda func:tryx(func,False)):
			if conn is not None:
				print('type(conn)',type(conn))
				#try_async(lambda:handle_bin(conn)) # sth wrong for threading in qmt
				tryx(lambda:handle_bin(conn))  # tmp using sync mode....
			if server_i==0:
				print('break loop')
				break
		if server_i==0:
			print('break loop 2')
			break

	print('start_server ends.')

def stop_server():
	global server,server_i
	if server_i >0:
		print('stop_server')
		tryx(server.close)
		del server
		server = None
		server_i=0
	print('stop_server ends.',type(server),server_i)

def init(ContextInfo):

	global g_ctx
	g_ctx = ContextInfo

	#try_async(start_server) # NOTES: qmt failed async start...
	start_server() # sync start...
	print('inited ends')

def stop(ContextInfo):

	try_async(stop_server)
	print('stop done.')

